/* 
    dev_app.js
    
    for demo of following items:
        1) passport-local authentication
        2) mongoose
        3) express framework
        4) prevent brute-force attacks by enforcing a maximum number of failed login attempts.
        5) google's recaptcha
        6) bcrypt-nodejs hashing
        7) server side session
        8) server side session with flash for deliver variables from node to jade
        9) server side session with flash for retrieve return url
        10) model prototype late binding for adding methods or functions
        11) login with remember-me options
        12) Regex ignore case search

*/



// variables
var express     = require('express');
var mongoose    = require('mongoose');
var passport    = require('passport');
var LocalStrategy = require('passport-local');
var bodyParser  = require('body-parser');
var cookieParser = require('cookie-parser');
var session     = require('express-session');
var bcrypt      = require('bcrypt-nodejs');
var flash       = require('connect-flash');
var myexport    = require('./my_export.js');

var MAX_LOGIN_ATTEMPTS = 5;               //
var LOCK_TIME   = 1000 * 60 * 60 * 2;     // 2 HOURS

myexport.prototype.def = function(){
   console.log('Late-binding prototype function: \'def\''); 
};
var m = new myexport();
m.def();



// Mongoose Schema ================================================================================
var userSchema = mongoose.Schema({
    email           : String,
    password        : String,
    remember_me     : String,    // remember_me hash for cookie compare
    login_attempts  : Number,
    lock_until      : Date
});
userSchema.methods.generateHash = function(password) {
   return bcrypt.hashSync(password, bcrypt.genSaltSync(4), null);
};
userSchema.methods.validPassword = function(password) {
   return bcrypt.compareSync(password, this.password);
};
var User = mongoose.model('user', userSchema);
// End Mongoose Schema ============================================================================


// Passport Area ==================================================================================
passport.serializeUser(function(user, done) { 
    done(null, { uid: user._id, another_thing: "some values" });
});
passport.deserializeUser(function(cookie_obj, done) {
    User.findById(cookie_obj.uid, function(err, user) {
        done(err, user);
    });
});
passport.use('my-local-signup', new LocalStrategy({
    usernameField : 'email',
    passwordField : 'password',
    passReqToCallback : true // allows us to pass back the entire request to the callback
},  function(req, email, password, done) {
    var regex = new RegExp(["^", email, "$"].join(""),"i");
    User.findOne({ 'email' :  regex }, function(err, user) {
        if (err) return done(err);
        if (user) {
            return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
        } else {
            var newUser             = new User();
            newUser.email           = email;
            newUser.password        = newUser.generateHash(password); // use the generateHash function in our user model
            newUser.login_attempts  = 1;
            newUser.save(function(err) {
                if (err) throw err;
                return done(null, newUser);
            });
        }
    });
}));
passport.use('my-local-login', new LocalStrategy({
    usernameField : 'email',
    passwordField : 'password',
    passReqToCallback : true 
}, function(req, email, password, done) {
    var regex = new RegExp(["^", email, "$"].join(""),"i");
    User.findOne({ 'email' :  regex }, function(err, user) {
        if (err) return done(err);
        
        // User not found
        if (!user) {
            // THIS IS THE CASE WE NEED TO PREVENT FOR BRUTE - FORCE
            // THIS WILL INVOKE RECAPCHA
            return done(null, false, req.flash('loginMessage', 'Wrong username or password.'));
        }
        
        // User found but locked
        if( user.lock_until > Date.now()){
            return done(null, false, req.flash('loginMessage', 'Your account is temporarily locked until: ' + user.lock_until ));

         // User found but with wrong password
        }else if (!user.validPassword(password)) {
        
            // Not reach max attempts yet
            if(user.login_attempts < MAX_LOGIN_ATTEMPTS){
                User.update({_id: user._id}, {$inc: {login_attempts: 1}}, function(err, num){
                    if(err) throw err;
                    return done(null, false, req.flash('loginMessage', 'Your account will be locked for 2 hours if you failed in next ' + (MAX_LOGIN_ATTEMPTS - user.login_attempts) + " times."));
                });
             
             // or Now reached the max attempts -> reset login_attempts
            }else{
                User.update({_id: user._id}, {login_attempts: 1, lock_until: (Date.now() + LOCK_TIME)}, function(err, num){
                    if(err) throw err;
                    return done(null, false, req.flash('loginMessage', 'Your account is now locked.'));
                });
            }
            
         // User found with correct password 
        }else{
            User.update({_id: user._id}, {login_attempts: 1}, function(err, num){
                if(err) throw err;
                return done(null, user);
            });
        }
        
    });
}));
// End of Passport Area ===========================================================================




var app = express();
app.listen(80);
app.locals.pretty = true;
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(cookieParser());
app.use(session( {secret: 'my_super_secrete_word', resave: true, saveUninitialized: true } ));
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
// my own version of remember me middleware
app.use(function(req, res, next){
    if(req.user) return next();
    var crd = req.cookies.remember_me;
    if(!crd) return next();
    
    User.findOne({remember_me: crd}, function(err, user) {
        if(err) {
            res.clearCookie('remember_me');
            return next();
        }else{
            req.login(user, function(err){
                return next();
            });
        }
    });
});


mongoose.connect('mongodb://localhost:27017/testdb', function(err){
    if(err)  {
        console.log('cannot connect db');
        process.exit(1);
    }
});



//=======================
// Route functions
//-----------------------------------------

app.isLoggedIn = function(req, res, next){
    if (req.isAuthenticated())  return next();
    req.flash('returnUrl', req.url);
    res.redirect('/users/login');
}



app.route('/users/logout')
    .get(function(req, res){
        res.clearCookie('remember_me');
        req.logout();
        res.redirect('/');
    });

app.route('/users/login')
    .get(function(req, res){
        res.render('login', { message: req.flash('loginMessage') });
    })

    .post(passport.authenticate('my-local-login',  { 
        /*successRedirect : '/', */ 
        failureRedirect : '/users/login',   
        failureFlash : true
     }), function(req, res, next) {
        if (!req.body.remember_me) { return next(); }
        var token = bcrypt.hashSync(req.user._id + Date.now(), bcrypt.genSaltSync(4), null);
        User.update({_id: req.user._id}, {remember_me: token}, function(err, num){
            if(err) return next();
            res.cookie('remember_me', token, { path: '/', httpOnly: true, maxAge: 1000*60*60*24*2 }); // 2 days
            return next();
        });
     }, function(req, res) {
        var returnUrl = req.flash('returnUrl');
        if(returnUrl == undefined || returnUrl == null || returnUrl.length ==0)
            res.redirect('/');
         else
            res.redirect(returnUrl);
    });

app.route('/users/signup')
    .get(function(req, res) {
        res.render('signup', { message: req.flash('signupMessage') });
    })

    .post(passport.authenticate('my-local-signup', {
        successRedirect : '/', 
        failureRedirect : '/users/signup', 
        failureFlash : true
    }));
    
app.route('/')
    .get(function(req, res, next){
        req.isAuthenticated();
        next();
    }, function(req, res) {
        res.render('index', {user: req.user});
    });
    
app.route('/test')
    .get(app.isLoggedIn, function(req, res){
        res.render('test', {user: req.user});
    });
    
app.route('/help')
    .get(app.isLoggedIn, function(req, res){
        res.render('help', {user: req.user});
    });

app.route('*')
    .get(function(err, req, res, next){
        res.send(402, err);
    });


